package com.xci.sys.service;

import com.github.pagehelper.Page;
import com.xci.core.annotation.DataScope;
import com.xci.core.annotation.Log;
import com.xci.core.annotation.Valid;
import com.xci.core.base.BaseService;
import com.xci.core.base.RestMessage;
import com.xci.core.domain.HistoryInfo;
import com.xci.core.exceptions.AppException;
import com.xci.core.helper.CacheHelper;
import com.xci.core.helper.Helper;
import com.xci.core.internal.Const;
import com.xci.sys.dao.RoleDao;
import com.xci.sys.dao.RoleDeptMapDao;
import com.xci.sys.dao.RoleModuleMapDao;
import com.xci.sys.entity.*;
import com.xci.sys.filter.RoleFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * 角色服务
 */
@Slf4j
@Service
public class RoleService extends BaseService {
    @Resource
    private RoleDao roleDao;//角色数据层对象
    @Resource
    private Cache roleCache;//角色缓存对象
    @Resource
    private UserService userService;//用户服务
    @Resource
    private RoleModuleMapDao roleModuleMapDao;//角色模块关联数据层对象
    @Resource
    private RoleDeptMapDao roleDeptMapDao;//角色机构数据层对象

    /**
     * 新建角色
     * @param entity 角色对象
     */
    @Transactional(rollbackFor = Exception.class)
    @Log(module = Const.Module.Role, msg = "新建角色")
    public RestMessage insert(@Valid SysRole entity) {
        return save(entity, true);
    }

    /**
     * 修改角色
     * @param entity 角色对象
     */
    @Transactional(rollbackFor = Exception.class)
    @Log(module = Const.Module.Role, msg = "修改角色")
    public RestMessage update(@Valid SysRole entity) {
        return save(entity, false);
    }


    /**
     * 批量保存角色对象
     * @param entities 角色对象集合
     */
    @Log(module = Const.Module.Role, msg = "批量保存角色对象")
    @Transactional(rollbackFor = Exception.class)
    public RestMessage batchSave(@Valid List<SysRole> entities) {
        for(SysRole role : entities) {
            if(Helper.isBlank(role.getId())) {                    //无主键,则新增
                save(role, true).ifFailException();
            } else if(!roleDao.existById(role.getId())) {   //主键不存在,则新增
                save(role, true).ifFailException();
            } else {                                            //否则修改
                save(role, false).ifFailException();
            }
        }
        return RestMessage.success();
    }

    /**
     * 修改角色状态
     * @param ids 角色主键字符串,多个逗号隔开
     * @param status 角色状态
     */
    @Transactional(rollbackFor = Exception.class)
    @Log(module = Const.Module.Role, msg = "修改角色状态")
    public RestMessage updateStatus(@NotBlank(message = "请指定角色主键") String ids,
            @NotNull(message = "请指定角色状态") Boolean status) {
        String[] idList = Helper.splitToArray(ids);
        for(String id : idList) {
            roleDao.updateStatus(id, status);
            roleCache.evict(id);
        }
        return RestMessage.success();
    }

    /**
     * 删除角色
     * @param ids 角色主键字符串,多个逗号隔开
     */
    @Transactional(rollbackFor = Exception.class)
    @Log(module = Const.Module.Role, msg = "删除角色")
    public RestMessage delete(@NotBlank(message = "请指定角色主键多个逗号隔开") String ids) {
        SysUser currentUser = getCurrentUser();
        String[] idList = Helper.splitToArray(ids);
        for(String id : idList) {
            SysRole old = roleDao.selectById(id);
            if(old == null) throw new AppException(Helper.format("无效的角色主键:[{}]", id));

            if(!currentUser.getIsAdmin() && old.getId().equals(currentUser.getRoleId()))//管理员除外
                throw new AppException(Helper.format("角色[{}]删除失败,不允许删除当前用户所属角色,必须由上级机构账号来操作", old.getName()));

            if(userService.existByRoleId(id)) {//检测角色是否被账号引用
                throw new AppException(Helper.format("用户中引用了角色:[{}],不允许删除", old.getName()));
            }

            roleDao.delete(id);
            roleCache.evict(id);
            roleModuleMapDao.deleteByRoleId(id);    //删除角色对应的模块信息
            roleDeptMapDao.deleteByRoleId(id);      //删除角色对应的机构信息
            saveDeleteHistory(HistoryInfo.builder().pk(id).before(old).build());
        }
        return RestMessage.success();
    }

    /**
     * 检测是否存在指定机构主键的角色
     * @param deptId 机构主键
     * @return 如果存在返回true, 否则返回false
     */
    public Boolean existByDeptId(String deptId) {
        return roleDao.existByDeptId(deptId);
    }

    /**
     * 检测角色编码唯一性
     * @param code 角色编码
     * @param id 角色主键
     * @return 如果存在返回true, 否则返回false
     */
    public Boolean existByCode(@NotBlank(message = "请指定角色编码") String code, String id) {
        return roleDao.existByCode(code, id);
    }

    /**
     * 检查角色名称唯一性
     * @param deptId 机构主键
     * @param name 角色名称
     * @param id 角色主键
     * @return 如果存在返回true, 否则返回false
     */
    public Boolean existByName(@NotBlank(message = "请指定机构主键") String deptId, @NotBlank(message = "请指定角色名称") String name,
            String id) {
        return roleDao.existByName(deptId, name, id);
    }

    /**
     * 查询单个角色对象
     * @param id 角色主键
     * @return 返回角色对象
     */
    public SysRole selectById(@NotBlank(message = "请指定角色主键") String id) {
        return CacheHelper.getCacheValue(roleCache, id, () -> roleDao.selectById(id));
    }

    /**
     * 根据编码查询单个角色对象
     * @param code 角色编码
     * @return 返回角色对象
     */
    public SysRole selectByCode(@NotBlank(message = "请指定角色编码") String code) {
        return roleDao.selectByCode(code);
    }

    /**
     * 获取用户所属角色
     * @param userId 用户主键
     */
    public SysRole selectByUserId(@NotBlank(message = "请指定用户主键") String userId) {
        SysUser user = userService.selectById(userId);
        return selectByUserId(user);
    }

    /**
     * 获取用户所属角色
     * @param user 用户对象
     */
    public SysRole selectByUserId(SysUser user) {
        if(user == null) return null;
        SysRole role = null;
        String roleId = user.getRoleId();
        if(Helper.isNotBlank(roleId)) {
            role = selectById(roleId);
        }
        return role;
    }

    /**
     * 查询角色列表
     * @param filter 过滤条件
     * @return 返回角色列表
     */
    @DataScope
    public List<SysRole> selectList(RoleFilter filter) {
        return roleDao.selectList(filter);
    }

    /**
     * 查询角色分页列表
     * @param filter 过滤条件
     * @return 返回角色分页列表
     */
    @DataScope
    public Page<SysRole> selectPageList(RoleFilter filter) {
        return roleDao.selectPageList(filter);
    }

    /**
     * 刷新用户缓存
     */
    public void refreshCache() {
        roleCache.clear();
        List<SysRole> list = roleDao.selectList(new RoleFilter());
        for(SysRole entity : list) {
            roleCache.put(entity.getId(), entity);
        }
        log.info("刷新系统角色缓存");
    }

    // region 模块

    /**
     * 保存角色关联模块
     * @param roleId 角色主键
     * @param moduleIds 模块主键数组
     */
    @Transactional(rollbackFor = Exception.class)
    public RestMessage saveModuleMap(@NotBlank(message = "请指定角色主键") String roleId, String[] moduleIds) {
        roleModuleMapDao.deleteByRoleId(roleId);
        if(!ObjectUtils.isEmpty(moduleIds)) {
            for(var moduleId : moduleIds) {
                var entity = new SysRoleModuleMap();
                entity.setRoleId(roleId);
                entity.setModuleId(moduleId.trim());
                roleModuleMapDao.insert(entity);
            }
        }
        return RestMessage.success();
    }

    /**
     * 根据角色主键删除关联模块
     * @param roleId 角色主键
     */
    @Transactional(rollbackFor = Exception.class)
    public RestMessage deleteModuleMap(@NotBlank(message = "请指定角色主键") String roleId) {
        roleModuleMapDao.deleteByRoleId(roleId);
        return RestMessage.success();
    }

    /**
     * 根据角色主键查询关联模块主键集合
     * @param roleId 角色主键
     */
    public List<String> selectModuleMapArray(@NotBlank(message = "请指定角色主键") String roleId) {
        return roleModuleMapDao.selectArrayByRoleId(roleId);
    }

    /**
     * 根据角色主键查询关联模块对象列表
     * @param roleId 角色主键
     */
    public List<SysModule> selectModuleMapList(@NotBlank(message = "请指定角色主键") String roleId) {
        return roleModuleMapDao.selectListByRoleId(roleId);
    }
    // endregion

    // region 机构

    /**
     * 保存角色关联机构
     * @param roleId 角色主键
     * @param deptScope 数据范围 [全部_1,所在机构_2,所在机构及所有下级_3,自定义_4,仅本人_5]
     * @param deptIds 机构主键数组
     */
    @Transactional(rollbackFor = Exception.class)
    public RestMessage saveDeptMap(@NotBlank(message = "请指定角色主键") String roleId,
            @NotNull(message = "请指定角色数据范围") Integer deptScope, String[] deptIds) {
        var role = selectById(roleId);
        if(role == null) {
            return RestMessage.fail("无效的角色主键");
        }
        role.setDeptScope(deptScope);
        roleDao.updateScope(roleId, deptScope);
        roleDeptMapDao.deleteByRoleId(roleId);
        if(deptScope == 4 && !ObjectUtils.isEmpty(deptIds)) {
            for(var deptId : deptIds) {
                var entity = new SysRoleDeptMap();
                entity.setRoleId(roleId);
                entity.setDeptId(deptId.trim());
                roleDeptMapDao.insert(entity);
            }
        }
        return RestMessage.success();
    }

    /**
     * 根据角色主键删除关联机构
     * @param roleId 角色主键
     */
    @Transactional(rollbackFor = Exception.class)
    public RestMessage deleteDeptMap(@NotBlank(message = "请指定角色主键") String roleId) {
        roleDeptMapDao.deleteByRoleId(roleId);
        return RestMessage.success();
    }

    /**
     * 根据角色主键查询关联机构主键集合
     * @param roleId 角色主键
     */
    public List<String> selectDeptMapArray(@NotBlank(message = "请指定角色主键") String roleId) {
        return roleDeptMapDao.selectArrayByRoleId(roleId);
    }

    /**
     * 根据角色主键查询关联机构列表
     * @param roleId 角色主键
     */
    public List<SysDept> selectDeptMapList(@NotBlank(message = "请指定角色主键") String roleId) {
        return roleDeptMapDao.selectListByRoleId(roleId);
    }

    // endregion

    /**
     * 保存角色
     * @param entity 角色对象
     * @param created 是否新建
     */
    private RestMessage save(SysRole entity, boolean created) {
        //自动添加主键
        if(created && Helper.isBlank(entity.getId())) {
            entity.setId(Helper.nextIdStr());
        }
        if(Helper.isBlank(entity.getSpell())) {
            entity.setSpell(Helper.getSpell(entity.getName()));
        }
        if(entity.getDeptScope() != null) {
            entity.setDeptScope(2);
        }

        var currentUser = getCurrentUser();
        if(!created && !currentUser.getIsAdmin() && entity.getId().equals(
                currentUser.getRoleId())) {//修改数据时,当前用户不能修改自己所属角色,只有上级账号才能修改(管理员除外)
            return RestMessage.fail("修改角色[" + entity.getName() + "]失败，当前用户不能修改自身所属角色,只有上级机构账号才能修改");
        }

        String existId = created ? null : entity.getId();
        //角色编码唯一性验证
        if(roleDao.existByCode(entity.getCode(), existId)) {
            String msg = Helper.format("角色编码[{}]已经存在", entity.getCode());
            return RestMessage.fail(msg);
        }

        //角色名称唯一性验证
        if(roleDao.existByName(entity.getDeptId(), entity.getName(), existId)) {
            String msg = Helper.format("角色名称[{}]已经存在", entity.getName());
            return RestMessage.fail(msg);
        }

        if(created) {// 新建
            roleDao.insert(entity);
            saveInsertHistory(
                    HistoryInfo.builder().pk(entity.getId()).after(roleDao.selectById(entity.getId())).build());
        } else {//修改
            var old = roleDao.selectById(entity.getId());
            roleDao.update(entity);
            saveUpdateHistory(HistoryInfo.builder().pk(entity.getId()).before(old).after(entity).build());
        }
        roleCache.evict(entity.getId());
        return RestMessage.success();
    }
}